package org.geotools.visualizadorsig.view;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import dbf.DBFdata;
import dbf.ModeloTabela;
import features.ShapeReference;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.MapContent;
import org.geotools.map.MapLayer;
import org.geotools.renderer.RenderListener;
import org.geotools.swing.JMapPane;
import org.geotools.swing.MapLayerTable;
import org.geotools.swing.StatusBar;



import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Panel;
import java.awt.Point;
import java.awt.ScrollPane;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import net.miginfocom.swing.MigLayout;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;

import org.geotools.map.MapContext;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.geotools.swing.action.InfoAction;
import org.geotools.swing.action.PanAction;
import org.geotools.swing.action.ResetAction;
import org.geotools.swing.action.ZoomInAction;
import org.geotools.swing.action.ZoomOutAction;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.event.MapMouseEvent;
import org.geotools.swing.styling.JSimpleStyleDialog.GeomType;
import org.geotools.swing.tool.CursorTool;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;

public class JMapFrame extends JFrame{
    private JPanel panel;
    private JTable tabelaDBF = new JTable();
    private int regiaoSelecTabela;
    private JScrollPane scrollTabela = new JScrollPane();
    private File shapeFile = new File("");
    private String lastPathFile = "lastPath.txt";
    private SimpleFeatureSource featureSource;

    private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);
    private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);

    protected boolean enableSelection = false;
    
    public static Color SELECTED_COLOUR = Color.YELLOW;
    private static final Color LINE_COLOUR = Color.BLUE;
    private static final Color FILL_COLOUR= Color.CYAN;
    private static final float OPACITY = 1.0f;
    private static final float LINE_WIDTH = 1.0f;
    private static final float POINT_SIZE = 10.0f;
    private String geometryAttributeName;

    private GeomType geometryType;
    private JButton selectButton;
    private JButton abrirArquivoButton;

    public enum Tool {
        NONE, INFO,PAN, RESET, ZOOM;
    }

    private Set<Tool> toolSet;

    public JMapPane mapPane;
    public StatusBar statusBar;
    private MapLayerTable mapLayerTable;
    private JToolBar toolBar;

    private boolean showStatusBar;
    private boolean showLayerTable;
    private boolean uiSet;

    private List<ShapeReference> referencias = new ArrayList<ShapeReference>();
    private JScrollPane scrollPane = new JScrollPane();

    /**
     * Creates a new {@code JMapFrame} object with a toolbar, map pane and status
     * bar; sets the supplied {@code MapContext}; and displays the frame on the
     * AWT event dispatching thread. The context's title is used as the frame's
     * title.
     *
     * @param context the map context containing the layers to display
     */
    public static void showMap(MapContext context) throws IOException {

        final JMapFrame frame = new JMapFrame(context);
        String path = new java.io.File(".").getCanonicalPath();

        frame.setTitle("Visualizador SIG");
        frame.setIconImage(new ImageIcon(path + "/src/main/java/org/geotools/visualizadorsig/view/images/visual_sig.png").getImage());
        frame.enableLayerTable(true);
        frame.enableStatusBar(true);
        frame.enableToolBar(true);
        frame.initComponents();

        frame.setExtendedState(MAXIMIZED_BOTH);
        frame.setSize(500, 500);


        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                frame.setVisible(true);
            }
        });
    }

    public JMapFrame() {
        this(null);
    }

    public JMapFrame(MapContext context) {
        this(context, new StreamingRenderer());
    }

    public JMapFrame(MapContext context, GTRenderer renderer) {
        super(context == null ? "" : context.getTitle());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        showLayerTable = false;
        showStatusBar = false;
        toolSet = new HashSet<Tool>();

        // the map pane is the one element that is always displayed
        mapPane = new JMapPane();
        mapPane.setBackground(Color.WHITE);
        mapPane.setMapContext(context);
        mapPane.setRenderer(renderer);
    }

    public void enableToolBar(boolean state) {
        if (state) {
            toolSet.addAll(EnumSet.allOf(Tool.class));
        } else {
            toolSet.clear();
        }
    }

    public void enableTool(Tool ...tool) {
        for (Tool t : tool) {
            toolSet.add(t);
        }
    }

    public void enableStatusBar(boolean state) {
        showStatusBar = state;
    }

    public void enableLayerTable(boolean state) {
        showLayerTable = state;
    }

    @Override
    public void setVisible(boolean state) {
        if (state && !uiSet) {
            try {
                initComponents();
            } catch (IOException ex) {
                Logger.getLogger(JMapFrame.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        super.setVisible(state);
    }

    public void initComponents() throws IOException {
        if (uiSet) {
            return;
        }

        StringBuilder sb = new StringBuilder();
        if (!toolSet.isEmpty()) {
            sb.append("[]"); // fixed size
        }
        sb.append("[grow]"); // map pane and optionally layer table fill space
        if (showStatusBar) {
            sb.append("[]"); // status bar height
        }

        panel = new JPanel(new MigLayout(
                "wrap 1, insets 0", // layout constrains: 1 component per row, no insets

                "[grow]", // column constraints: col grows when frame is resized

                sb.toString() ));

        if (!toolSet.isEmpty()) {
            buildToolBar();

        if (showLayerTable) {
            mapLayerTable = new MapLayerTable(mapPane);
            mapLayerTable.setPreferredSize(new Dimension(200, -1));
            JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, mapLayerTable, mapPane);
            panel.add(splitPane, "grow");
        } else {
            panel.add(mapPane, "grow");
        }

        if (showStatusBar) {
            panel.add(scrollTabela);
            scrollTabela.show(false);
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            final Dimension screenSize = toolkit.getScreenSize();
            Dimension dimension = new Dimension((int) (screenSize.getWidth()), 99);
            scrollTabela.getVerticalScrollBar().setBlockIncrement(16);
            scrollTabela.setMaximumSize(dimension);
            scrollTabela.setMinimumSize(dimension);
        }

        this.getContentPane().add(panel);
        uiSet = true;
     }

        initEvents();
    }
    
    private void initEvents(){
        tabelaDBF.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if(e.getClickCount() % 2 != 0)
                    return;

                try {
                    Point p = e.getPoint();
                    int row = tabelaDBF.rowAtPoint(p)+1;
                    String linha =  "" + row;
                    selectFeatures2(linha);
                    mapPane.repaint();
                } catch (Exception ex) {
                    Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        
        selectButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                enableSelection = !enableSelection;
                try {
                    String path = new java.io.File(".").getCanonicalPath();
                    String image = (enableSelection ? "select_on" : "select_off") + ".png";
                    selectButton.setIcon(new ImageIcon(path + "/src/main/java/org/geotools/visualizadorsig/view/images/"+image));
                    JMapFrame.super.validateTree();
                    JMapFrame.super.repaint();
                } catch (IOException ex) {
                    Logger.getLogger(JMapFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
         });
        
        mapPane.setCursorTool(new CursorTool() {
            @Override
            public void onMouseClicked(MapMouseEvent ev) {
                if(!enableSelection)
                    return;
                
                if(ev.getClickCount() % 2 != 0)
                    return;
                selectFeatures(ev);
                tabelaDBF.setRowSelectionInterval(regiaoSelecTabela-1, regiaoSelecTabela-1);
                int selectedRowIndex = tabelaDBF.getSelectedRow();
                int local = selectedRowIndex *  scrollTabela.getVerticalScrollBar().getBlockIncrement();
                scrollTabela.getVerticalScrollBar().getModel().setValue(local);
            }
        });
        
        abrirArquivoButton.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        try {
                            shapeFile = JFileDataStoreChooser.showOpenFile("shp", getLastPath(), null);
                            setLastPath(shapeFile.getAbsolutePath());
                            abrirMapa(shapeFile);
                        } catch (IOException ex) {
                            Logger.getLogger(JMapFrame.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
    }
    void selectFeatures2(String row) throws IOException {
        SimpleFeatureCollection selectedFeatures = featureSource.getFeatures();

        SimpleFeatureIterator iter = selectedFeatures.features();
        Set<FeatureId> IDs = new HashSet<FeatureId>();

        while (iter.hasNext()) {
            SimpleFeature feature = iter.next();
            String aux ="";
            for(int i = 0; i < feature.getID().toString().length(); i++){
                Character caractere = feature.getID().charAt(i);
                if(!Character.isDigit(caractere))
                    continue;
                aux += caractere;
            }
            if(!aux.equals(row))
                continue;
            
            IDs.add(feature.getIdentifier());
            System.out.println(feature.getIdentifier() + " OK");
            break;
          }
        displaySelectedFeatures(IDs);
      }

    public void displaySelectedFeatures(Set<FeatureId> IDs) {
        Style style;

        if (IDs.isEmpty()) 
            style = createDefaultStyle();
        else
            style = createSelectedStyle(IDs);

        Layer layer = getMapContext().layers().get(0);
        ((FeatureLayer) layer).setStyle(style);
        mapPane.moveImage(0, 0);
        mapPane.show(true);
        mapPane.repaint();
        super.validateTree();
        super.repaint();
    }
    
    void selectFeatures(MapMouseEvent ev) {
        Point screenPos = ev.getPoint();
        Rectangle screenRect = new Rectangle(screenPos.x-2, screenPos.y-2, 5, 5);
        AffineTransform screenToWorld = getMapPane().getScreenToWorldTransform();
        Rectangle2D worldRect = screenToWorld.createTransformedShape(screenRect).getBounds2D();
        ReferencedEnvelope bbox = new ReferencedEnvelope(worldRect, getMapContext().getCoordinateReferenceSystem());
        Filter filter = ff.intersects(ff.property(geometryAttributeName), ff.literal(bbox));
        try {
            SimpleFeatureCollection selectedFeatures = featureSource.getFeatures(filter);
            SimpleFeatureIterator iter = selectedFeatures.features();
            Set<FeatureId> IDs = new HashSet<FeatureId>();
            try {
                while (iter.hasNext()) {
                    SimpleFeature feature = iter.next();
                    IDs.add(feature.getIdentifier());
                    int k, tamanhoVariavel = feature.getIdentifier().toString().length();
                    for (k = 0; k < tamanhoVariavel; k++) {
                        if (!Character.isDigit(feature.getIdentifier().toString().charAt(tamanhoVariavel-1)))
                            break;
                        tamanhoVariavel--;

                    }
                    regiaoSelecTabela = Integer.parseInt(feature.getIdentifier().toString().substring(feature.getIdentifier().toString().length() - k, feature.getIdentifier().toString().length()));
                    System.out.println("aqui2 ");
                    System.out.println("   " + feature.getIdentifier());
                }
            } finally {
                iter.close();
            }

            if (IDs.isEmpty())
                System.out.println("   no feature selected");

            displaySelectedFeatures(IDs);
        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }
    
    private Style createDefaultStyle() {
        Rule rule = createRule(LINE_COLOUR, FILL_COLOUR);

        FeatureTypeStyle fts = sf.createFeatureTypeStyle();
        fts.rules().add(rule);

        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }


    private Style createSelectedStyle(Set<FeatureId> IDs) {
        Rule selectedRule = createRule(SELECTED_COLOUR, SELECTED_COLOUR);
        selectedRule.setFilter(ff.id(IDs));

        Rule otherRule = createRule(LINE_COLOUR, FILL_COLOUR);
        otherRule.setElseFilter(true);

        FeatureTypeStyle fts = sf.createFeatureTypeStyle();
        fts.rules().add(selectedRule);
        fts.rules().add(otherRule);

        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

    private void buildToolBar() throws IOException {
        toolBar = new JToolBar();
        toolBar.setOrientation(JToolBar.HORIZONTAL);
        
        String path = new java.io.File(".").getCanonicalPath();
        abrirArquivoButton = new JButton();
        abrirArquivoButton.setIcon(new ImageIcon(path + "/src/main/java/org/geotools/visualizadorsig/view/images/abrir3.png"));
        abrirArquivoButton.setToolTipText("Selecionar arquivo para exibir");
        toolBar.add(abrirArquivoButton);
        toolBar.addSeparator();
        panel.add(toolBar, "grow");

        toolBar.setFloatable(false);
        ButtonGroup cursorToolGrp = new ButtonGroup();
        if (toolSet.contains(Tool.ZOOM)) {
            JButton zoomInBtn = new JButton(new ZoomInAction(mapPane));
            zoomInBtn.setToolTipText("Zoom In - Clique para ampliar ou arraste para definir a área de zoom");
            toolBar.add(zoomInBtn);
            cursorToolGrp.add(zoomInBtn);

            JButton zoomOutBtn = new JButton(new ZoomOutAction(mapPane));
            zoomOutBtn.setToolTipText("Zoom Out - Clique para reduzir o zoom centrado na posição do cursor");
            toolBar.add(zoomOutBtn);
            cursorToolGrp.add(zoomOutBtn);

            JButton resetBtn = new JButton(new ResetAction(mapPane));
            resetBtn.setToolTipText("Reset Zoom - Mostrar a extensão total de todas as camadas exibidas");
            toolBar.add(resetBtn);
            cursorToolGrp.add(resetBtn);

            toolBar.addSeparator();
        }
        if (toolSet.contains(Tool.PAN)) {
            JButton panBtn = new JButton(new PanAction(mapPane));
            panBtn.setToolTipText("Mover imagem - Clique e arraste para mover");
            toolBar.add(panBtn);
            cursorToolGrp.add(panBtn);
            toolBar.addSeparator();
        }
        if (toolSet.contains(Tool.INFO)) {
            InfoAction infoAction = new InfoAction(mapPane);
            JButton infoBtn = new JButton(infoAction);
            infoBtn.setToolTipText("Obter Informações - Clique para identificar características no layer selecionado");
            toolBar.add(infoBtn);
            toolBar.addSeparator();
        }
        
        selectButton = new JButton();
        selectButton.setIcon(new ImageIcon(path + "/src/main/java/org/geotools/visualizadorsig/view/images/select_off.png"));
        selectButton.setToolTipText("Link entre ponto do mapa e a tabela de atributos");
        toolBar.add(selectButton);
        toolBar.addSeparator();
    }

    private void abrirMapa(File file) throws IOException {
        if (file == null)
            return;
        FileDataStore store = FileDataStoreFinder.getDataStore(file);
        featureSource = store.getFeatureSource();
        MapContext map = new DefaultMapContext();

        ShapeReference reference = new ShapeReference(map.getTitle(), file.getAbsolutePath());
        if(!referencias.contains(reference)){
            referencias.add(reference);
        }

        setTitle("Visualizador SIG - "+featureSource.getName().getLocalPart());
        setGeometry();
        Style style = createDefaultStyle();
        map.addLayer(featureSource, style);
        mapPane.show(false);
        mapPane.reset();
        setMapContext(map);
        mapPane.moveImage(0, 0);
        mapPane.show(true);

        if(showStatusBar){
            refreshStatusBar();
        }
        super.validateTree();
        super.repaint();
    }

    private Rule createRule(Color outlineColor, Color fillColor) {
        Symbolizer symbolizer = null;
        Fill fill = null;
        Stroke stroke = sf.createStroke(ff.literal(outlineColor), ff.literal(LINE_WIDTH));

        switch (geometryType) {
            case POLYGON:
                fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
                symbolizer = sf.createPolygonSymbolizer(stroke, fill, geometryAttributeName);
                break;

            case LINE:
                symbolizer = sf.createLineSymbolizer(stroke, geometryAttributeName);
                break;

            case POINT:
                fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));

                Mark mark = sf.getCircleMark();
                mark.setFill(fill);
                mark.setStroke(stroke);

                Graphic graphic = sf.createDefaultGraphic();
                graphic.graphicalSymbols().clear();
                graphic.graphicalSymbols().add(mark);
                graphic.setSize(ff.literal(POINT_SIZE));

                symbolizer = sf.createPointSymbolizer(graphic, geometryAttributeName);
        }

        Rule rule = sf.createRule();
        rule.symbolizers().add(symbolizer);
        return rule;
    }

    private void setGeometry() {
        GeometryDescriptor geomDesc = featureSource.getSchema().getGeometryDescriptor();
        geometryAttributeName = geomDesc.getLocalName();

        Class<?> clazz = geomDesc.getType().getBinding();

        if (Polygon.class.isAssignableFrom(clazz) ||
                MultiPolygon.class.isAssignableFrom(clazz)) {
            geometryType = GeomType.POLYGON;

        } else if (LineString.class.isAssignableFrom(clazz) ||
                MultiLineString.class.isAssignableFrom(clazz)) {
            geometryType = GeomType.LINE;

        } else {
            geometryType = GeomType.POINT;
        }

    }

    private File getLastPath(){
        String lastPath = "";
        try {
            FileReader reader = new FileReader(new File(lastPathFile));
            BufferedReader bufferedReader = new BufferedReader(reader);
            lastPath = bufferedReader.readLine();
            reader.close();
            bufferedReader.close();
        } catch (Exception ex) {
            Logger.getLogger(JMapFrame.class.getName()).log(Level.SEVERE, null, ex);
            lastPath = "";
        }
        return new File(lastPath);
    }

    private void setLastPath(String lastPath){
        try {
            FileWriter writer = new FileWriter(new File(lastPathFile));
            writer.flush();
            writer.write(lastPath);
            writer.close();
        } catch (IOException ex) {
            Logger.getLogger(JMapFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void refreshStatusBar() throws IOException{
        String dbf = shapeFile.toString();
        dbf= dbf.substring(0, dbf.length() - 3);
        dbf = dbf + "dbf";
        File arquivoTabela = new File (dbf);

        int numeroColunasDBF = DBFdata.numeroColunas(arquivoTabela);
        int numeroLinhasDBF = DBFdata.numeroLinhas(arquivoTabela);
        
        tabelaDBF.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);// ativa barra de rolagem horizontal
        tabelaDBF.setModel(new ModeloTabela(DBFdata.cabecalhoTabela(arquivoTabela), numeroLinhasDBF, numeroColunasDBF));
        for (int i = 0; i < numeroLinhasDBF; i++) {
                for (int j = 0; j <numeroColunasDBF; j++) {
                    tabelaDBF.setValueAt(DBFdata.matrizDados(arquivoTabela,i,j), i, j);
                }
        }
        scrollTabela.setViewportView(tabelaDBF);
        scrollTabela.setVisible(true);
    }

    /**
     * Get the map context associated with this frame.
     * Returns {@code null} if no map context has been set explicitly with the
     * constructor or {@linkplain #setMapContext}.
     *
     * @return the current {@code MapContext} object
     */
    public MapContext getMapContext() {
        return mapPane.getMapContext();
    }

    /**
     * Set the MapContext object used by this frame.
     *
     * @param context a MapContext instance
     * @throws IllegalArgumentException if context is null
     */
    public void setMapContext(MapContext context) {
        if (context == null)
            throw new IllegalArgumentException("context must not be null");
        
        mapPane.setMapContext(context);
    }

    /**
     * Get the renderer being used by this frame.
     * Returns {@code null} if no renderer was set via the constructor
     * or {@linkplain #setRenderer}.
     *
     * @return the current {@code GTRenderer} object
     */
    public GTRenderer getRenderer() {
        return mapPane.getRenderer();
    }

    /**
     * Set the renderer to be used by this frame.
     *
     * @param renderer a GTRenderer instance
     * @throws IllegalArgumentException if renderer is null
     */
    public void setRenderer(GTRenderer renderer) {
        if (renderer == null) {
            throw new IllegalArgumentException("renderer must not be null");
        }

        mapPane.setRenderer(renderer);
    }

    /**
     * Provides access to the instance of {@code JMapPane} being used
     * by this frame.
     *
     * @return the {@code JMapPane} object
     */
    public JMapPane getMapPane() {
        return mapPane;
    }

    /**
     * Provides access to the toolbar being used by this frame.
     * If {@linkplain #initComponents} has not been called yet
     * this method will invoke it.
     *
     * @return the toolbar or null if the toolbar was not enabled
     */
    public JToolBar getToolBar() throws IOException {
        if (!uiSet) initComponents();
        return toolBar;
    }
}

